﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Commander.Core.AI {
  public interface IGeneticReproducer<T, E> {
    T NextGen { get; }
    void PerformCrossover();
    void PerformMutation();
    void Reproduce(T pop);
    E Crossover(E parentA, E parentB);
    E Mutate(E enc);
    double MutationRate { get; }
    double CrossoverRate { get; }
    int PopulationSize { get; }
  }

  public class GeneticReproducer : IGeneticReproducer<Population, Encoding> {
		private Encoding m_ancestor;

    public GeneticReproducer(double mutation, double crossover, int size, Encoding ancestor) {
      m_crossoverRate = crossover;
      m_mutationRate = mutation;
      m_populationSize = size;
      m_population = EncodingGenerator.GenerateInitialPopulation(ancestor, size);
			m_ancestor = ancestor;
			Commander.Core.Refactorings.Modifiers.NetWalkerCodeModifier.OnSpeculativeGenExtraInforRequired += new Refactorings.Modifiers.NetWalkerCodeModifier.SpeculativeGeneralityExtraInfoRequired(CodeModifier_OnSpeculativeGenExtraInforRequired);
    }

		string CodeModifier_OnSpeculativeGenExtraInforRequired(string variantClass) {
			return m_ancestor.FilePath;
		}

    public void PerformCrossover() {
      m_population.EvaluateAll();
      int crossovers = (int)(m_populationSize * CrossoverRate);
      int highlanders = m_populationSize - crossovers;

      m_population.Sort();

      int top_tier = m_populationSize / 3;
      int middle_tier = (top_tier * 2);
      int bottom_tiers = m_populationSize - top_tier;
      Random rand = new Random();
      m_nextGen = new Population();

      //crossing best and second bests
      for (int i = 0; i < crossovers / 3; i++) {
        int parA = rand.Next(0, top_tier);
        int parB = rand.Next(top_tier, middle_tier);
        m_nextGen.Add(Crossover(m_population[parA], m_population[parB]));
      }

      //crossing best and worst
      for (int i = 0; i < crossovers / 3; i++) {
        int parA = rand.Next(0, top_tier);
        int parB = rand.Next(middle_tier, m_populationSize);
        m_nextGen.Add(Crossover(m_population[parA], m_population[parB]));
      }

      //crossing second best and worst
      for (int i = 0; i < (crossovers - (crossovers / 3)); i++) {
        int parA = rand.Next(top_tier, middle_tier);
        int parB = rand.Next(middle_tier, m_populationSize);
        m_nextGen.Add(Crossover(m_population[parA], m_population[parB]));
      }

      Trim_Fill(rand);
    }

    private void Trim_Fill(Random rand) {
      //filling up any remaining spots, if any
      while (m_nextGen.Count < m_populationSize) {
        m_nextGen.Add(m_population[rand.Next(0, m_populationSize)]);
      }
      //trimming out any excess elements, if any
      while (m_nextGen.Count > m_populationSize) {
        m_nextGen.RemoveAt(rand.Next(0, m_populationSize));
      }
    }

    public void PerformMutation() {
      int mutations = (int)(m_populationSize * MutationRate);
      Random rand = new Random();
      List<int> mutated = new List<int>();
      for (int i = 0; i < mutations; i++) {
        int mutated_index = rand.Next(0, m_populationSize);

        //makes sure the encoding was not previously mutated
        while (mutated.Contains(mutated_index)) {
          mutated_index = rand.Next(0, m_populationSize);
        }

        Encoding mutant = Mutate(m_nextGen[mutated_index]);
        m_nextGen[mutated_index] = mutant;
        mutated.Add(mutated_index);
      }

    }

    public void MoveNext() {
      m_population = new Population(m_nextGen);
      m_nextGen.Clear();
    }

    public void Reproduce(Population pop) {
      m_population = pop;
      PerformCrossover();
    }

    /// <summary>
    /// Create offsrping from a 2 point crossover of 8 item encoding
    /// </summary>
    /// <param name="parentA"></param>
    /// <param name="parentB"></param>
    /// <remarks>parents must have 8 items</remarks>
    /// <returns></returns>
    public Encoding Crossover(Encoding parentA, Encoding parentB) {

      Random rand = new Random();
      int index_i = rand.Next(1, parentA.Count/2);
      int index_j = rand.Next(parentA.Count/2, parentA.Count-1);

      Encoding offspring = parentA.Clone();//better to implemt cloning

      offspring[index_i] = parentB[index_i];
      offspring[index_i - 1] = parentB[index_i - 1];

      offspring[index_j] = parentB[index_j];
      offspring[index_j + 1] = parentB[index_j + 1];

      return offspring;
    }

    public Encoding Mutate(Encoding enc) {
      return EncodingGenerator.MixList(enc);
    }

    double m_mutationRate;
    public double MutationRate {
      get { return m_mutationRate; }
    }

    double m_crossoverRate;
    public double CrossoverRate {
      get { return m_crossoverRate; }
    }

    int m_populationSize;
    public int PopulationSize {
      get { return m_populationSize; }
    }

    Population m_population;

    public Population Population {
      get { return m_population; }
      private set { m_population = value; }
    }
    Population m_nextGen;
    public Population NextGen {
      get { return m_nextGen; }
      private set { m_nextGen = value; }
    }
  }
}
